﻿using System;
using System.Collections.Generic;
using System.IO;

namespace Red.Core.Network.PacketNetwork
{
    /// <summary>
    /// Exception thrown when format is invalid (i.e. wrong magic number, incorrect CRC etc.).
    /// </summary>
    internal class InvalidFormatException : Exception
    {
        public InvalidFormatException( string message )
            : base( message )
        {

        }
    }

    /// <summary>
    /// Helper class for FormatReader, to make CRC checking easier.
    /// </summary>
    internal class FormatBlock
    {
        public long BeginPosition;
        public int Size;

        public FormatBlock(long beginPositionParam, int sizeParam)
        {
            BeginPosition = beginPositionParam;
            Size = sizeParam;
        }
    }

    /// <summary>
    /// Helper class for packet format reading (CRC checks, magic checks etc.)
    /// </summary>
    internal class FormatReader
    {
        private BinaryReader _reader;
        private Stack<long> _blockPositions = new Stack<long>();

        public delegate uint CalculateCrc32Handler(byte[] buffer);

        public FormatReader(Stream stream)
        {
            _reader = new BinaryReader(stream);
        }

        public long GetStreamPosition()
        {
            return _reader.BaseStream.Position;
        }

        private void ReportInvalidFormat( string message )
        {
            throw new InvalidFormatException( message  );
        }

        public int GetCurrentOffset()
        {
            return (int)_reader.BaseStream.Position;
        }

        public bool CheckSize(int requiredSize)
        {
            return requiredSize <= (int)_reader.BaseStream.Length;
        }

        public void CheckInt32(int value)
        {
            int valueToCheck = _reader.ReadInt32();
            if ( valueToCheck != value )
                ReportInvalidFormat( "Invalid int32 check (magic number).");
        }

        public int ReadInt32()
        {
            return _reader.ReadInt32();
        }

        public uint ReadUInt32()
        {
            return _reader.ReadUInt32();
        }

        public byte[] ReadBytes(int count)
        {
            return _reader.ReadBytes(count);
        }

        public void BeginBlock()
        {
            _blockPositions.Push(_reader.BaseStream.Position);
        }

        public FormatBlock EndBlock()
        {
            long blockPosition = _blockPositions.Pop();
            FormatBlock block = new FormatBlock(blockPosition, (int)(_reader.BaseStream.Position - blockPosition));
            return block;
        }

        public void CheckBlockCrc32(FormatBlock block, uint expectedCrc, CalculateCrc32Handler crcCalculator)
        {
            long currentStreamPosition = _reader.BaseStream.Position;

            _reader.BaseStream.Seek(block.BeginPosition, SeekOrigin.Begin);

            byte[] blockData = _reader.ReadBytes(block.Size);
            uint calculatedCrc = crcCalculator(blockData);

            if (expectedCrc != calculatedCrc)
            {
                ReportInvalidFormat( "Invalid CRC." );
            }

            _reader.BaseStream.Seek(currentStreamPosition, SeekOrigin.Begin);
        }
    }
}
